package com.secure.util;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.*;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;

import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Map;

/**
 * RSA工具类
 * 公钥/密钥/签名都经过base64编码
 * <p>
 * hutool库SecureUtil有各种加解密算法工具方法
 *
 * @author duchao
 */
public class RsaUtil {

    /**
     * 生成key对
     *
     * @return
     */
    public static KeyPair generateKeyPair() {
        return SecureUtil.generateKeyPair(AsymmetricAlgorithm.RSA.getValue());
    }

    /**
     * 获取公钥
     *
     * @param keyPair 密钥对
     * @return
     */
    public static String getPublicKey(KeyPair keyPair) {
        return Base64Encoder.encode(keyPair.getPublic().getEncoded());
    }

    /**
     * 获取私钥
     *
     * @param keyPair 密钥对
     * @return
     */
    public static String getPrivateKey(KeyPair keyPair) {
        return Base64Encoder.encode(keyPair.getPrivate().getEncoded());
    }

    /**
     * map转k1=v1&k2=v2格式字符串
     * <p>
     * 按照map里元素顺序组成k1=v1&k2=v2格式
     *
     * @param plainMap 明文map
     * @return
     */
    public static String map2KeyValuePairText(Map<String, Object> plainMap) {
        if (CollectionUtil.isEmpty(plainMap)) {
            return null;
        }
        plainMap = JSONUtil.toBean(JSONUtil.toJsonStr(plainMap), JSONConfig.create().setIgnoreNullValue(true), Map.class);
        StringBuilder plainTextBuilder = new StringBuilder();
        for (Map.Entry<String, Object> entry : plainMap.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (ObjectUtil.isNotNull(value)) {
                String valueString = (ObjectUtil.isBasicType(value) || value instanceof String ? value.toString() : JSONUtil.toJsonStr(value));
                plainTextBuilder.append(key).append("=").append(valueString).append("&");
            }
        }
        return plainTextBuilder.substring(0, plainTextBuilder.length() - 1);
    }

    /*********************************消费方使用*********************************/

    /**
     * 签名,base64编码
     *
     * <p>
     * 按照map里元素顺序组成k1=v1&k2=v2格式字符串进行签名
     *
     * @param plainMap   明文map
     * @param privateKey 私钥
     * @return
     * @throws Exception
     */
    public static String sign(Map<String, Object> plainMap, String privateKey) {
        String plainText = map2KeyValuePairText(plainMap);
        Sign sign = SecureUtil.sign(SignAlgorithm.MD5withRSA, privateKey, null);
        byte[] signatureBytes = sign.sign(plainText, StandardCharsets.UTF_8);
        return Base64Encoder.encode(signatureBytes);
    }

    /**
     * 签名
     * <p>
     * 按照map里元素顺序组成k1=v1&k2=v2格式字符串进行签名
     *
     * @param obj        明文对象
     * @param privateKey 私钥
     * @return
     * @throws Exception
     */
    public static String sign(Object obj, String privateKey) {
        Map<String, Object> plainMap = JSONUtil.toBean(JSONUtil.toJsonStr(obj), new TypeReference<Map<String, Object>>() {
        }, true);
        String plainText = map2KeyValuePairText(plainMap);
        Sign sign = SecureUtil.sign(SignAlgorithm.MD5withRSA, privateKey, null);
        byte[] signatureBytes = sign.sign(plainText, StandardCharsets.UTF_8);
        return Base64Encoder.encode(signatureBytes);
    }

    /**
     * 加密明文
     * 注意：
     * 加密后产生的密文在网络调用传输过程最好先经过urlencode，因为加号等特殊符号会丢失，
     * 则对应的解密密文则需要先经过urldecode
     *
     * @param object    对象
     * @param publicKey 公钥
     * @return
     */
    public static String encryptByPublicKey(Object object, String publicKey) {
        String plainText = JSONUtil.toJsonStr(object);
        RSA rsa = SecureUtil.rsa(null, publicKey);
        return rsa.encryptBase64(plainText, StandardCharsets.UTF_8, KeyType.PublicKey);
    }

    /**
     * 解密密文
     *
     * @param encryptText 密文文本
     * @param publicKey   公钥
     * @return
     */
    public static String decryptByPublicKey(String encryptText, String publicKey) {
        RSA rsa = SecureUtil.rsa(null, publicKey);
        return rsa.decryptStr(encryptText, KeyType.PublicKey, StandardCharsets.UTF_8);
    }

    /**
     * 解密密文
     *
     * @param encryptText 密文文本
     * @param publicKey   公钥
     * @return
     */
    public static <T> T decryptByPublicKey(String encryptText, String publicKey, Class<T> clazz) {
        RSA rsa = SecureUtil.rsa(null, publicKey);
        String plainText = rsa.decryptStr(encryptText, KeyType.PublicKey, StandardCharsets.UTF_8);
        return JSONUtil.toBean(plainText, clazz);
    }

    /**
     * 解密密文
     *
     * @param encryptText 密文文本
     * @param publicKey   公钥
     * @return
     */
    public static <T> T decryptByPublicKey(String encryptText, String publicKey, TypeReference<T> typeReference) {
        RSA rsa = SecureUtil.rsa(null, publicKey);
        String plainText = rsa.decryptStr(encryptText, KeyType.PublicKey, StandardCharsets.UTF_8);
        return JSONUtil.toBean(plainText, typeReference, true);
    }

    /*********************************生产方使用*********************************/

    /**
     * 验签
     * 按照map里元素顺序组成k1=v1&k2=v2格式
     *
     * @param keyValuePairPlainText k1=v1&k2=v2格式明文文本
     * @param publicKey             公钥
     * @return
     */
    public static boolean verify(String keyValuePairPlainText, String signatureText, String publicKey) {
        Sign sign = SecureUtil.sign(SignAlgorithm.MD5withRSA, null, publicKey);
        return sign.verify(keyValuePairPlainText.getBytes(StandardCharsets.UTF_8), Base64Decoder.decode(signatureText));
    }

    /**
     * 解密密文
     *
     * @param encryptText 密文
     * @param privateKey  私钥
     * @return
     */
    public static String decryptByPrivateKey(String encryptText, String privateKey) {
        RSA rsa = SecureUtil.rsa(privateKey, null);
        return rsa.decryptStr(encryptText, KeyType.PrivateKey, StandardCharsets.UTF_8);
    }

    /**
     * 解密密文
     *
     * @param encryptText 密文
     * @param privateKey  私钥
     * @return
     */
    public static <T> T decryptByPrivateKey(String encryptText, String privateKey, Class<T> clazz) {
        RSA rsa = SecureUtil.rsa(privateKey, null);
        String plainText = rsa.decryptStr(encryptText, KeyType.PrivateKey, StandardCharsets.UTF_8);
        return JSONUtil.toBean(plainText, clazz);
    }

    /**
     * 解密密文
     *
     * @param encryptText 密文
     * @param privateKey  私钥
     * @return
     */
    public static <T> T decryptByPrivateKey(String encryptText, String privateKey, TypeReference<T> typeReference) {
        RSA rsa = SecureUtil.rsa(privateKey, null);
        String plainText = rsa.decryptStr(encryptText, KeyType.PrivateKey, StandardCharsets.UTF_8);
        return JSONUtil.toBean(plainText, typeReference, true);
    }

    /**
     * 加密明文
     *
     * @param object     对象
     * @param privateKey 私钥
     * @return
     */
    public static String encryptByPrivateKey(Object object, String privateKey) {
        RSA rsa = SecureUtil.rsa(privateKey, null);
        String plainText = JSONUtil.toJsonStr(object);
        return rsa.encryptBase64(plainText, StandardCharsets.UTF_8, KeyType.PrivateKey);
    }

}